package com.csap.framework.apidoc.admin.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.csap.framework.annotation.apidoc.Description;
import com.csap.framework.apidoc.admin.Shell;
import com.csap.framework.apidoc.admin.config.MyFInalConfig;
import com.csap.framework.apidoc.admin.engine.FreemarkerEngine;
import com.csap.framework.apidoc.admin.entity.ApiDocEntity;
import com.csap.framework.apidoc.admin.entity.ApiDocPathEntity;
import com.csap.framework.apidoc.admin.model.ApiDocModel;
import com.csap.framework.apidoc.admin.model.ApiDocPathModel;
import com.csap.framework.apidoc.admin.model.PushSuccess;
import com.csap.framework.apidoc.admin.model.freemark.ConfigModel;
import com.csap.framework.apidoc.admin.model.freemark.FreemarkModel;
import com.csap.framework.apidoc.admin.mybatis.ApiDocMapper;
import com.csap.framework.apidoc.admin.service.ApiDocPathService;
import com.csap.framework.apidoc.admin.service.ApiDocService;
import com.csap.framework.extension.CsapException;
import com.csap.framework.util.CollectionsUtil;
import com.csap.framework.util.FileUtil;
import com.csap.framework.util.StringUtil;
import com.csap.framework.util.exception.ExceptionUtils;
import com.csap.framework.util.http.HttpClientUtil;
import com.csap.framework.util.map.CsapMap;
import com.csap.framework.util.optional.Optional;
import com.csap.framework.util.time.TimeUtil;
import com.csap.mybatisplus.core.toolkit.StringPool;
import com.csap.mybatisplus.extension.service.impl.BaseCsapServiceImpl;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 文档管理信息
 * </p>
 *
 * @author
 * @dateTime
 */
@Service
@Slf4j
@Description(value = "文档管理信息ServiceImpl")
public class ApiDocServiceImpl extends BaseCsapServiceImpl<ApiDocMapper, ApiDocEntity, ApiDocModel, Long> implements ApiDocService {
    @Resource
    private ApiDocPathService pathService;
    /**
     * 文档项目地址
     */
    @Value("${apidocPath:}")
    private String apidocPath;
    /**
     * 编译后 文档存放的主目录
     */
    @Value("${htmlPath:}")
    private String htmlPath;
    @Resource
    private FreemarkerEngine freemarkerEngine;
    private Map<Long, PushSuccess> success = new ConcurrentHashMap<>();
    @Resource
    private TaskExecutor taskExecutor;

    private String getHomePath() {
        return apidocPath + MyFInalConfig.HOME;
    }

    /**
     * 路径集合
     */
    private Set<String> paths = Sets.newHashSet();

    /**
     * 刷新文档
     *
     * @param docIds 需要刷新的文档ID列表
     * @return
     */
    @Override
    public Boolean flushDoc(List<Long> docIds) {
        return lambdaQuery()
                .in(ApiDocEntity::getId, docIds)
                .listOptional()
                .then(i -> paths.forEach(i2 -> FileUtil.deleteDirectory(i2)))
                .then(i -> {
                    List<ConfigModel> configModelList = i.stream().map(ii -> new ConfigModel(ii.convert()).setDest(htmlPath + ii.getPath()))
                            .map(i1 -> {
                                List<ApiDocPathModel> list = pathService
                                        .lambdaQuery()
                                        .eq(ApiDocPathEntity::getApidocId, i1.getDocModel().getId())
                                        .orderByDesc(ApiDocPathEntity::getSeq)
                                        .listOptional()
                                        .transformList(ApiDocPathModel.class)
                                        .stream()
                                        .map(this::foreachDoc)
                                        .filter(i2 -> i2 != null)
                                        .collect(Collectors.toList());
                                i1.setPathList(list);
                                return i1;
                            }).collect(Collectors.toList());
                    writeDocConfig(configModelList);
                })
                .value(i -> true);
    }

    /**
     * 写入配置文件
     *
     * @param configModelList
     */
    @Override
    public void writeDocConfig(List<ConfigModel> configModelList) {
        try {
            //TODO config.js
            freemarkerEngine.writer(CsapMap.
                    <String, Object>build()
                    .add("data", configModelList.get(0)), "config.js.ftl", getHomePath() + ".vuepress/config.js");
            //首页 README.md
            freemarkerEngine.writer(CsapMap.
                    <String, Object>build()
                    .add("data", configModelList.get(0).getDocModel()), "README.md.ftl", getHomePath() + "README.md");
            //简介 README.md
            freemarkerEngine.writer(CsapMap.
                    <String, Object>build()
                    .add("md", configModelList.get(0).getDocModel().getMd())
                    .add("categories", "/doc")
                    .add("path", "/doc")
                    .add("dateTime", TimeUtil.date2string(new Date())), "doc/README.md.ftl", getHomePath() + StringPool.SLASH + "doc/README.md");

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CsapException("文档执行异常,请查询运行日志,联系技术人员。");
        }
    }

    /**
     * 循环处理文档
     *
     * @param docModel 文档信息
     * @return
     */
    @Override
    public ApiDocPathModel foreachDoc(ApiDocPathModel docModel) {
        try {
            String string = HttpClientUtil.httpGet(docModel.getUrl());
            if (StringUtil.isEmpty(string)) {
                throw new RuntimeException();
            }
            JSONObject jsonObject = JSON.parseObject(string);
            if (jsonObject.containsKey("data")) {
                jsonObject = jsonObject.getJSONObject("data");
            }
            String path = getHomePath() + StringPool.SLASH + docModel.getPathKey();
            FileUtil.deleteDirectory(path);
            paths.add(path);
            FileUtil.makeDirectory(path);
            jsonObject.put("docPath", docModel);
            JSONArray jsonArray = jsonObject.getJSONArray("apiList");
            jsonArray.forEach(i -> {
                JSONObject jsonObject1 = (JSONObject) i;
                JSONArray jsonArray1 = jsonObject1.getJSONArray("methodList");
                List<FreemarkModel> list = new ArrayList<>();
                jsonArray1.forEach(i2 -> list.add(loopMethodList((JSONObject) i2, jsonObject1, getPath(jsonObject1.getString("name")), docModel)));
                jsonObject1.put("methodList", list);
            });
            docModel.setApiDoc(jsonObject);
            //简介 README.md
            freemarkerEngine.writer(CsapMap.
                    <String, Object>build()
                    .add("md", docModel.getMd())
                    .add("categories", docModel.getPathKey())
                    .add("path", StringPool.SLASH + docModel.getPathKey())
                    .add("dateTime", TimeUtil.date2string(new Date())), "doc/README.md.ftl", path + "/README.md");
            return docModel;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CsapException(String.format("文档数据访问错误，请检查连接是否可访问:%s", docModel.getUrl()));
        }
    }

    /**
     * 循坏写入方法文件
     *
     * @param methodObject    方法对象
     * @param parentApi       父类相关数据
     * @param parentPath      父级目录
     * @param apiDocPathModel 文档路径信息
     */
    @SneakyThrows
    private FreemarkModel loopMethodList(JSONObject methodObject, JSONObject parentApi, String parentPath, ApiDocPathModel apiDocPathModel) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(StringPool.SLASH)
                .append(apiDocPathModel.getPathKey())
                .append(StringPool.SLASH)
                .append(parentPath)
                .append(StringPool.SLASH);
        FreemarkModel freemarkModel = new FreemarkModel();
        freemarkModel.setDateTime(TimeUtil.date2string(new Date()));
        freemarkModel.setTitle(methodObject.getString("value"));
        freemarkModel.setPageLink(stringBuilder.append(methodObject.getString("name")).toString());
        freemarkModel.setCategories(Arrays.asList(apiDocPathModel.getPathKey()));
        List<String> list = Lists.newArrayList(parentApi.getString("value") + "-" + methodObject.getString("value"));
        list.addAll(getTagsPath(methodObject.getJSONArray("apiPath").toJavaList(String.class), methodObject.getJSONArray("paths").toJavaList(String.class)));
        freemarkModel.setTags(list);
        freemarkModel.setContent(methodObject.toJSONString());
        freemarkerEngine
                .writer(CsapMap.<String, Object>build().add("data", freemarkModel), "apidoc-content.md.ftl", getHomePath() + StringPool.SLASH + freemarkModel.getPageLink() + ".md");
        return freemarkModel;
    }

    /**
     * 获取标签路径
     *
     * @param apiPath     父类路径
     * @param methodPaths 接口路径
     * @return
     */
    private List<String> getTagsPath(List<String> apiPath, List<String> methodPaths) {
        if (CollectionsUtil.isEmpty(apiPath)) {
            return methodPaths;
        }
        if (CollectionsUtil.isEmpty(methodPaths)) {
            return apiPath;
        }
        return apiPath.stream()
                .flatMap(i -> methodPaths.stream().flatMap(i2 -> Stream.of(StringPool.SLASH + i + StringPool.SLASH + i2)))
                .collect(Collectors.toList());
    }

    private String getPath(String name) {
        String[] string = name.split("\\.");
        return string[string.length - 1];
    }

    @Override
    public String publishDoc(Long id) {
        return lambdaQuery()
                .eq(ApiDocEntity::getId, id)
                .oneOptional()
                .isNullThrow(i -> ExceptionUtils.mpe("文档不存在"))
                .when(i -> !success.containsKey(id), i -> {
                    success.put(id, PushSuccess.builder().success(Boolean.FALSE).content(Collections.synchronizedList(new ArrayList<>())).build());
                    taskExecutor.execute(() -> {
                        Shell.runShell(Arrays.asList("cd " + apidocPath,
                                "npm run docs:build --max_old_space_size=4096"), success.get(id).getContent());
                        success.get(id).setSuccess(Boolean.TRUE);
                    });
                    return id.toString();
                }, i -> id.toString());
    }

    @Override
    public PushSuccess publishSuccess(String id) {
        return Optional.of(success.get(id))
                .when(i -> i.getSuccess(), i -> success.remove(id))
                .get();
    }
}
